package com.develop.bluetoothlib.operator.impl;

import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelUuid;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.RequiresApi;

import com.develop.bluetoothlib.MyBluetooth;
import com.develop.bluetoothlib.entity.MyBluetoothDevice;
import com.develop.bluetoothlib.inter.ScanCallBack;
import com.develop.bluetoothlib.operator.abstra.Scanner;
import com.develop.bluetoothlib.utils.ScanFilterUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;


public class BluetoothScanner extends Scanner {

    private int scanType, continuousScanTimes;
    private              boolean               isScanning;
    private              Context               mContext;
    private              ScanCallBack          scanCallBack;
    private              BluetoothLeScanner    bluetoothLeScanner;
    private              List<MyBluetoothDevice> myBluetoothDevices;
    private              BluetoothAdapter      bluetoothAdapter;
    private              Handler               handler;
    private              boolean               liveUpdateScannedDeviceName;
    private              long                  lastCheckPeriodScanNumLimitTime  = 0L;
    private static final int                   PERIOD_SCAN_NUM_LIMIT_DELAY_TIME = 30 * 1000;

    private BluetoothScanner() {
    }

    public BluetoothScanner(Context context, BluetoothAdapter bluetoothAdapter) {
        this.mContext = context;
        this.bluetoothAdapter = bluetoothAdapter;
    }


    @Override
    public synchronized void scan(int scanType, ScanCallBack scanCallBack) {
        startScan(scanType, 0, scanCallBack);
    }

    @Override
    public synchronized void scan(int scanType, int timeUse, ScanCallBack scanCallBack) {
        startScan(scanType, timeUse, scanCallBack);
    }


    private void startScan(int scanType, int timeUse, ScanCallBack scanCallBack) {
        try {
            //Important, If we're already discovering or scanning, stop it first!
            isScanning = false;
            stopScan();

            MyBluetooth.BleConfig bleConfig = MyBluetooth.getInstance().getBleConfig();
            UUID[] scanFilterServerUUIDs = null;
            if (bleConfig != null) {
                this.liveUpdateScannedDeviceName = bleConfig.isLiveUpdateScannedDeviceName();
                scanFilterServerUUIDs = bleConfig.getScanFilterServerUUIDs();
            }

            this.scanType = scanType;
            this.scanCallBack = scanCallBack;

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
                if (this.scanCallBack != null) {
                    this.scanCallBack.onError(1, "Only system versions above Android 4.3 are supported.");
                }
                return;
            }

            if (this.scanType == MyBluetoothDevice.DEVICE_TYPE_LE && !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
                if (this.scanCallBack != null) {
                    this.scanCallBack.onError(2, "Your device does not support low-power Bluetooth.");
                }
                return;
            }

            //Clear data before scan
            if (myBluetoothDevices == null) {
                myBluetoothDevices = new ArrayList<>();
            } else if (myBluetoothDevices.size() > 0) {
                myBluetoothDevices.clear();
            }

            if (this.scanCallBack != null) {
                this.scanCallBack.onScanStart();
            }

            if (this.scanType == MyBluetoothDevice.DEVICE_TYPE_CLASSIC) {
                // Register for broadcasts when a device is discovered
                // Register for broadcasts when discovery has finished
                IntentFilter filter = new IntentFilter(android.bluetooth.BluetoothDevice.ACTION_FOUND);
                filter.addAction(android.bluetooth.BluetoothDevice.ACTION_BOND_STATE_CHANGED);//状态改变
                filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);//行动扫描模式改变了
                filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//动作状态发生了变化
                filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
                filter.addAction(android.bluetooth.BluetoothDevice.ACTION_PAIRING_REQUEST);
                filter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
                filter.addAction(android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
                filter.addAction(android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECTED);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
                }else{
                    mContext.registerReceiver(mReceiver, filter);
                }

                if (!(isScanning = bluetoothAdapter.startDiscovery()) && this.scanCallBack != null) {
                    this.scanCallBack.onError(3, "Start discovery fail,make sure you have Bluetooth enabled or open permissions");
                }

            } else if (this.scanType == MyBluetoothDevice.DEVICE_TYPE_LE) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    //After 5.0 use BluetoothLeScanner to scan
                    //Because bluetoothAdapter.startLeScan deprecated
                    bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
                    if (bluetoothLeScanner != null) {
                        isScanning = true;

                        //Since Android 7.0 does not allow 5 consecutive scans within 30s, otherwise any device cannot be scanned
                        //Once this limit is exceeded, a prompt is given
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                            if (continuousScanTimes == 0) {
                                lastCheckPeriodScanNumLimitTime = System.currentTimeMillis();
                            }
                            continuousScanTimes++;
                            long checkPeriodScanNumTimeDiff = System.currentTimeMillis() - lastCheckPeriodScanNumLimitTime;
                            if (checkPeriodScanNumTimeDiff > PERIOD_SCAN_NUM_LIMIT_DELAY_TIME) {
                                continuousScanTimes = 0;
                            } else if (this.scanCallBack != null && continuousScanTimes > 5) {
                                this.scanCallBack.onError(3, "Forbidden,please do not scan more than 5 times in 30s");
                            }
                        }


                        if(scanFilterServerUUIDs != null && scanFilterServerUUIDs.length > 0){
                            //Scan by specify uuids
                            //Create filter and add
                            List<ScanFilter> filters = new ArrayList<>();
                            for (int i = 0; i < scanFilterServerUUIDs.length; i++) {
                                //通过指定uuid搜索
                                ScanFilter scanFilter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(scanFilterServerUUIDs[i])).build();
                                filters.add(scanFilter);
                            }
                            //Create scan settings
                            //ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
                            ScanSettings settings = new ScanSettings.Builder().build();
                            bluetoothLeScanner.startScan(filters,settings,mScanCallback);
                        }else {
                            bluetoothLeScanner.startScan(mScanCallback);
                        }

                    } else if (this.scanCallBack != null) {
                        this.scanCallBack.onError(3, "BluetoothLeScanner is null,make sure you have Bluetooth enabled or open permissions");
                    }

                } else if (!(isScanning = bluetoothAdapter.startLeScan(mLeScanCallBack)) && this.scanCallBack != null) {
                    this.scanCallBack.onError(3, "StartLeScan fail,make sure you have Bluetooth enabled or open permissions");
                }
            }

            //Auto stop scan when time out
            if (timeUse != 0) {
                if (handler == null) {
                    handler = new Handler(Looper.getMainLooper());
                }
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        stopScan();
                    }
                }, timeUse);
            }
        }catch (SecurityException e){
            Log.e("蓝牙连接","startScan SecurityException " + e.getMessage());
        }
    }

    //CLASSIC BLUETOOTH
    // The BroadcastReceiver that listens for discovered devices
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            try {
                String action = intent.getAction();

                // When discovery finds a device
                if (android.bluetooth.BluetoothDevice.ACTION_FOUND.equals(action)) {
                    // Get the BluetoothDevice object from the Intent
                    android.bluetooth.BluetoothDevice device = intent
                            .getParcelableExtra(android.bluetooth.BluetoothDevice.EXTRA_DEVICE);

                    //If there is filtering, filter the scanning results
                    if (getFilter() != null && !ScanFilterUtils.isInFilter(device.getName(), getFilter())) {
                        return;
                    }

                    int rssi = intent.getShortExtra(android.bluetooth.BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);

                    // new device found
                    MyBluetoothDevice myBluetoothDevice = new MyBluetoothDevice();
                    myBluetoothDevice.setPaired(device.getBondState() == android.bluetooth.BluetoothDevice.BOND_BONDED);
                    myBluetoothDevice.setAddress(device.getAddress());
                    myBluetoothDevice.setName(device.getName());
                    myBluetoothDevice.setType(device.getType());
                    myBluetoothDevice.setRssi(rssi);


                    if (myBluetoothDevices != null && myBluetoothDevices.size() > 0) {
                        if (myBluetoothDevices.contains(myBluetoothDevice)) {
                            return;
                        }
                    }
                    if(!TextUtils.isEmpty(myBluetoothDevice.getName())){
                        myBluetoothDevices.add(myBluetoothDevice);
                    }

                    if (scanCallBack != null) {
                        scanCallBack.onScanning(myBluetoothDevices, myBluetoothDevice);
                    }

                    // When discovery is finished, complete scan and call back
                } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                    if (scanCallBack != null) {
                        scanCallBack.onScanFinished(myBluetoothDevices);
                    }
                    isScanning = false;
                }

            }catch (SecurityException e){
                Log.e("蓝牙连接","onReceive SecurityException " + e.getMessage());
            }
        }
    };


    //Ble scan callback before 5.0
    private BluetoothAdapter.LeScanCallback mLeScanCallBack = new BluetoothAdapter.LeScanCallback() {

        @Override
        public void onLeScan(android.bluetooth.BluetoothDevice bluetoothDevice, int rssi, byte[] bytes) {
            handleBleScanResult(bluetoothDevice, rssi, bytes);
        }
    };


    //Ble scan callback after 5.0
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            android.bluetooth.BluetoothDevice bluetoothDevice = result.getDevice();

            handleBleScanResult(bluetoothDevice, result.getRssi(),
                    result.getScanRecord() == null ? null : result.getScanRecord().getBytes());
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
            if (scanCallBack != null) {
                scanCallBack.onError(errorCode, "Scan failed!");
            }
        }
    };


    private void handleBleScanResult(android.bluetooth.BluetoothDevice bluetoothDevice, int rssi, byte[] scanRecord) {
        try {
            //If you need to get real-time device name,we can parse it from scanRecord.
            //Otherwise, you will get cached data, not real-time data
            String deviceName;
            if (liveUpdateScannedDeviceName) {
                deviceName = ScanFilterUtils.parseDeviceName(scanRecord);
            } else {
                deviceName = bluetoothDevice.getName();
            }

            //If there is filtering, filter the scanning results
            if (getFilter() != null && !ScanFilterUtils.isInFilter(deviceName, getFilter())) {
                return;
            }

            final MyBluetoothDevice device = new MyBluetoothDevice();
            device.setName(deviceName);
            device.setAddress(bluetoothDevice.getAddress());
            device.setType(MyBluetoothDevice.DEVICE_TYPE_LE);
            device.setPaired(bluetoothDevice.getBondState() == android.bluetooth.BluetoothDevice.BOND_BONDED);
            device.setRssi(rssi);
            device.setScanRecord(scanRecord);

            if (myBluetoothDevices.contains(device)) {
                int index = myBluetoothDevices.indexOf(device);
                myBluetoothDevices.set(index, device);
            } else {
                if(!TextUtils.isEmpty(bluetoothDevice.getName())){
                    myBluetoothDevices.add(device);
                }
            }


            if (scanCallBack != null) {
                if (handler == null) {
                    handler = new Handler(Looper.getMainLooper());
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        scanCallBack.onScanning(myBluetoothDevices, device);
                    }
                });
            }
        }catch (SecurityException e){
            Log.e("蓝牙连接","handleBleScanResult SecurityException " + e.getMessage());
        }
    }

    @Override
    public synchronized void stopScan() {
        try {
            if (scanType == MyBluetoothDevice.DEVICE_TYPE_CLASSIC) {
                if (bluetoothAdapter.isDiscovering()) {
                    bluetoothAdapter.cancelDiscovery();
                }
                unregisterReceiver();
            } else if (scanType == MyBluetoothDevice.DEVICE_TYPE_LE && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && bluetoothLeScanner != null) {
                    bluetoothLeScanner.stopScan(mScanCallback);
                } else {
                    bluetoothAdapter.stopLeScan(mLeScanCallBack);
                }
            }

            if (isScanning) {
                if (handler == null) {
                    handler = new Handler(Looper.getMainLooper());
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (scanCallBack != null) {
                            scanCallBack.onScanFinished(myBluetoothDevices);
                        }
                    }
                });
                isScanning = false;
            }
        }catch (SecurityException e){
            Log.e("蓝牙连接","stopScan SecurityException " + e.getMessage());
        }

    }

    @Override
    public void resetCallBack() {
        scanCallBack = null;
    }

    private void unregisterReceiver() {
        try {
            mContext.unregisterReceiver(mReceiver);
        } catch (Exception e) {

        }
    }
}
